package domain;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Joachim
 */
public class Client_ {

    // Singleton
    private static Client_ instance;
    // Server settings & connection
    private static InetAddress host;
    private final int PORT = 1337;
    private Socket link;
    private PrintWriter printWriter; // Send text to the server
    private ObjectInputStream objectInputStream; // Receive objects from the server
    private BufferedReader bufferedReader;
    private String email;
    private String rootDirectory;
    //dunno 
    private File dir;
    ExecutorService exService;

    /*
     ** GENERAL
     */
    public static Client_ getInstance() {
        if (instance == null) {
            instance = new Client_();
        }
        return instance;
    }

    private Client_() {
        try {
            host = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            System.out.println("Host id not found!");
            System.exit(1);
        }
        
        //exService = Executors.newFixedThreadPool(2);
        //exService.submit(new DirectoryListener(this));
        //exService.submit(new ServerListener(bufferedReader, this));
    }

    public void setRootDirectory(String rootDirectory) throws InvalidPathException {
        File file = new File(rootDirectory);

        if (!file.exists()) {
            throw new InvalidPathException(rootDirectory, "Invalid directory path!");
        }
        if (!file.isDirectory()) {
            throw new InvalidPathException(rootDirectory, "Not a directory!");
        }
        
        this.rootDirectory = rootDirectory;
    }

    public String getRootDirectory() {
        return rootDirectory;
    }

    /*
     ** INITIALIZATION
     */
    private void initConnection() throws ConnectException {
        try {
            link = new Socket(host, PORT);
            printWriter = new PrintWriter(link.getOutputStream(), true); // To send text to the server
            objectInputStream = new ObjectInputStream(link.getInputStream()); // To receive objects from the server
            //bufferedReader = new BufferedReader(new InputStreamReader(link.getInputStream()));
            bufferedReader = new BufferedReader(new InputStreamReader(link.getInputStream()));
        } catch (IOException e) {
            throw new ConnectException("Connection with server failed");
        }
    }

    public void closeConnection() {
        try {
            printWriter.println("closeConnection");
            link.close();
        } catch (IOException ex) {
            // Exception -> connection is already closed so do nothing
        }
    }

    /*
     ** ACCOUNT
     */
    public boolean logIn(String email, String password) throws IllegalArgumentException, ConnectException {
        if (email.isEmpty() || password.isEmpty()) {
            throw new IllegalArgumentException("All fields are required");
        } else if (!Utils.isValidEmail(email)) {
            throw new IllegalArgumentException("Email is not valid");
        }
        initConnection(); // Open connection with server
        Boolean loginSuccess = false;
        try {
            printWriter.println("login");
            printWriter.println(email);
            printWriter.println(password);
            loginSuccess = (Boolean) objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new ConnectException("Connection with server failed");
        }
        if (loginSuccess) {
            this.email = email;
        }
        //closeConnection();
        return loginSuccess;
    }

    public boolean register(String email, String password, String repeatPassword) throws IllegalArgumentException, ConnectException {
        if (email.isEmpty() || password.isEmpty() || repeatPassword.isEmpty()) {
            throw new IllegalArgumentException("All fields are required");
        } else if (!Utils.isValidEmail(email)) {
            throw new IllegalArgumentException("Email is not valid");
        } else if (!password.equals(repeatPassword)) {
            throw new IllegalArgumentException("Passwords don't match");
        }
        initConnection(); // Open connection with server
        Boolean registerSuccess = false;
        try {
            printWriter.println("register");
            printWriter.println(email);
            printWriter.println(password);
            registerSuccess = (Boolean) objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new ConnectException("Connection with server failed");
        }
        closeConnection();
        return registerSuccess;
    }

    public boolean resetPassword(String email) throws IllegalArgumentException, ConnectException {
        if (!Utils.isValidEmail(email)) {
            throw new IllegalArgumentException("Email is not valid");
        }
        initConnection(); // Open connection with server
        Boolean resetPasswordSuccess = false;
        try {
            printWriter.println("resetPassword");
            printWriter.println(email);
            resetPasswordSuccess = (Boolean) objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new ConnectException("Connection with server failed");
        }
        //closeConnection();
        return resetPasswordSuccess;
    }

    /*
     ** CLIENT ACTIONS
     */
    public boolean sendFile(String relatiefPath) throws ConnectException {
        System.out.println("Now sending file: ");
        
        boolean uploadFileSuccess = false;
        printWriter.println("acceptFile"); // Make server ready to receive a file
        /*
         twee atributen in de constructor van File, 
         hiermee creeeren we een relatief path ten opzichte van de rootDirectory
         */
        File file = new File(rootDirectory, relatiefPath);

        //relatiefPath!!!!
        printWriter.println(dir.toURI().relativize(file.toURI()).getPath()); // Send file name (actually relatiefPath)
        printWriter.println(file.length()); // Send file size
        byte[] buffer = new byte[(int) file.length()];
        try {
            InputStream is = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            OutputStream os = link.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(os);
            int n;
            while ((n = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, n);
                System.out.println(n);
            }
            bos.flush();
            uploadFileSuccess = (Boolean) objectInputStream.readObject();
            bis.close();
            //bos.close();

            if (bufferedReader.readLine().equals("ok")) {
                uploadFileSuccess = true;
            }

        } catch (IOException e) {
            e.printStackTrace();
            throw new ConnectException("Connection with server failed");
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

        return uploadFileSuccess;
    }

    public boolean deleteServerFile(String fileAbsolutePath) {
        boolean deleteFileSuccess = false;

        try {
            printWriter.println("deleteFile");
            /*
             twee atributen in de constructor van File, 
             hiermee creeeren we een relatief path ten opzichte van de rootDirectory
             */
            File file = new File(rootDirectory, fileAbsolutePath);
            // File file = new File(rootDirectory + File.pathSeparator + fileName);
            printWriter.println(dir.toURI().relativize(file.toURI()).getPath());

            if (bufferedReader.readLine().equals("ok")) {
                deleteFileSuccess = true;
            }
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }

        return deleteFileSuccess;
    }

    
    /*
     public CheckFileResult checkFile(String fileName) throws ConnectException
     {
     CheckFileResult result = CheckFileResult.SYNCHED;
        
     try {
     File file = new File(rootDirectory + File.pathSeparator + fileName);
     long lastModified = Files.getLastModifiedTime(file.toPath()).toMillis();
     String mdstring = Utils.calculateFileHash(file);
            
     printWriter.println("checkFile");
     printWriter.println(file.getName());
     printWriter.println(lastModified);
     printWriter.println(mdstring);
            
     if(bufferedReader.readLine().equals("synched"))
     result = CheckFileResult.SYNCHED;
     else if(bufferedReader.readLine().equals("changed_client"))
     result = CheckFileResult.CHANGED_CLIENT;
     else if(bufferedReader.readLine().equals("changed_server"))
     result = CheckFileResult.CHANGED_SERVER;
     else if(bufferedReader.readLine().equals("notfound"))
     result = CheckFileResult.NOTFOUND;
     }
     catch(FileNotFoundException e) {
     e.printStackTrace();
     }
     catch(IOException e) {
     e.printStackTrace();
     }
        
     return result;
     }
     */
    
    
    /*
     ** SERVER ACTIONS
     */
    public void acceptFile() {
        try {

            String relatiefPath = bufferedReader.readLine(); // Receive file name 
            int fileSize = Integer.parseInt(bufferedReader.readLine()); // Receive file size
            
            
            
            
            /*InputStream is = link.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            Path path = Paths.get(rootDirectory + File.separatorChar + relatiefPath);
            File file = Utils.createFileAndEmptyParents(path, rootDirectory);

            OutputStream os = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(os);
            byte[] buffer = new byte[fileSize];
            int length, totalSize = 0;

            while (!(totalSize == fileSize)) {
                length = bis.read(buffer);
                bos.write(buffer, 0, length);
                totalSize += length;
            }
            bos.flush();
            bos.close();
            //bis.close();*/
            
            
            
            
            
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void deleteFile(String relatiefPath) {
        try {
            File file = new File(rootDirectory, relatiefPath);

            if (!file.exists()) {
                throw new IllegalArgumentException("Delete: no such file or directory: " + relatiefPath);
            }

            if (!file.canWrite()) {
                throw new IllegalArgumentException("Delete: write protected: " + relatiefPath);
            }

            Utils.deleteFileAndEmptyParents(file.toPath(), rootDirectory);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void deleteFile() {
        try {
            deleteFile(bufferedReader.readLine());
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     private void requestFile(String fileName) {
     throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
     }
     */
    /*
     ** SYNCHRONIZATION
     */
    /*
     private void synchronizeFile(String fileName) throws ConnectException
     {
     switch(checkFile(fileName))
     {
     case CHANGED_CLIENT:
     deleteServerFile(fileName);
     uploadFile(fileName);
     break;
     case CHANGED_SERVER:
     deleteFile(fileName);
     requestFile(fileName);
     break;
     case NOTFOUND:
     uploadFile(fileName);
     break;
     }
     }
    
     public void synchronizeFiles() throws ConnectException
     {
     File dir = new File(getRootDirectory());
        
     for (File file : dir.listFiles())
     {
     if(!file.isDirectory()) // voorlopig nog geen subdirectories
     {
     synchronizeFile(file.getName());
     }
     }
     }
     */
    public void synchronizeFiles() throws ConnectException {
        File dirp = new File(getRootDirectory());
        dir = dirp;
        dir.mkdir();
        
        HashMap<String, File> fileMap = Utils.getFileMap(dir);
        
        System.out.println("-----");
        for (String str : fileMap.keySet()) {
            System.out.println(str);
        }
        System.out.println("-----");

        printWriter.println("synchronize");

        try {
            String type = bufferedReader.readLine();
            String relatiefPath;
            long lastModified = 0;
            String hash = null;

            while (!type.equals("end")) {
                relatiefPath = bufferedReader.readLine();
                lastModified = Long.parseLong(bufferedReader.readLine());
                hash = bufferedReader.readLine();

                if (fileMap.containsKey(relatiefPath)) {
                    if (Utils.calculateFileHash(fileMap.get(relatiefPath)).equals(hash)) {
                        System.out.println(relatiefPath + " : synched");
                        printWriter.println("synched");
                    } else {
                        if (lastModified < Files.getLastModifiedTime(fileMap.get(relatiefPath).toPath()).toMillis()) {
                            System.out.println(relatiefPath + " : changed on client");
                            printWriter.println("changed_client");
                            sendFile(relatiefPath);
                        } else {
                            System.out.println(relatiefPath + " : changed on server");
                            printWriter.println("changed_server");
                            deleteFile(relatiefPath);
                            if(bufferedReader.readLine().equals("acceptFile"))
                                acceptFile();
                        }

                    }
                } else {
                    System.out.println(relatiefPath + " : not found");
                    printWriter.println("notfound");
                    if(bufferedReader.readLine().equals("acceptFile"))
                        acceptFile();
                    System.out.println(relatiefPath + " downloaded");
                }
                
                System.out.println("lezen");
                type = bufferedReader.readLine();
                System.out.println(type);
            }
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        System.out.println("ENDAAA!!!!!!");
    }
}